/*++

Copyright (c) 2005 - 2006, Intel Corporation                                                         
All rights reserved. This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 

Module Name:

  Ip4Output.c

Abstract:

  Transmit the IP4 packet

--*/
#include "Ip4Impl.h"

UINT16  mIp4Id;

EFI_STATUS
Ip4PrependHead (
  IN NET_BUF                *Packet,
  IN IP4_HEAD               *Head,
  IN UINT8                  *Option,
  IN UINT32                 OptLen
  )
/*++

Routine Description:

  Prepend an IP4 head to the Packet. It will copy the options and
  build the IP4 header fields. Used for IP4 fragmentation.

Arguments:

  Packet  - The packet to prepend IP4 header to 
  Head    - The caller supplied header. The caller should set the 
            following header fields: Tos, TotalLen, Id, Fragment,
            Ttl, Protocol, Src and Dst. All the fields are in host
            byte order. This function will fill in the Ver, HeadLen,
            and checksum.
  Option  - The orginal IP4 option to copy from
  OptLen  - The length of the IP4 option

Returns:

  EFI_BAD_BUFFER_SIZE - There is no enought room in the head space of Packet.
  EFI_SUCCESS         - The IP4 header is successfully added to the packet.

--*/
{
  UINT32                    HeadLen;
  UINT32                    Len;
  IP4_HEAD                  *PacketHead;
  BOOLEAN                   FirstFragment;

  //
  // Prepend the options: first get the option length, then copy it over.
  //
  HeadLen       = 0;
  FirstFragment = IP4_FIRST_FRAGMENT (Head->Fragment);

  Ip4CopyOption (Option, OptLen, FirstFragment, NULL, &Len);

  HeadLen = IP4_MIN_HEADLEN + Len;
  ASSERT (((Len %4) == 0) && (HeadLen <= IP4_MAX_HEADLEN));

  PacketHead = (IP4_HEAD *) NetbufAllocSpace (Packet, HeadLen, NET_BUF_HEAD);

  if (PacketHead == NULL) {
    return EFI_BAD_BUFFER_SIZE;
  }

  Ip4CopyOption (Option, OptLen, FirstFragment, (UINT8 *) (PacketHead + 1), &Len);

  //
  // Set the head up, convert the host byte order to network byte order
  //
  PacketHead->Ver       = 4;
  PacketHead->HeadLen   = (UINT8) (HeadLen >> 2);
  PacketHead->Tos       = Head->Tos;
  PacketHead->TotalLen  = HTONS (Packet->TotalSize);
  PacketHead->Id        = HTONS (Head->Id);
  PacketHead->Fragment  = HTONS (Head->Fragment);
  PacketHead->Checksum  = 0;
  PacketHead->Ttl       = Head->Ttl;
  PacketHead->Protocol  = Head->Protocol;
  PacketHead->Src       = HTONL (Head->Src);
  PacketHead->Dst       = HTONL (Head->Dst);
  PacketHead->Checksum  = ~NetblockChecksum ((UINT8 *) PacketHead, HeadLen);

  Packet->Ip            = PacketHead;
  return EFI_SUCCESS;
}

IP4_INTERFACE *
Ip4SelectInterface (
  IN IP4_SERVICE            *IpSb,
  IN IP4_ADDR               Dst,
  IN IP4_ADDR               Src
  )
/*++

Routine Description:

  Select an interface to send the packet generated in the IP4 driver 
  itself, that is, not by the requests of IP4 child's consumer. Such
  packets include the ICMP echo replies, and other ICMP error packets.

Arguments:

  IpSb  - The IP4 service that wants to send the packets.
  Dst   - The destination of the packet
  Src   - The source of the packet

Returns:

  NULL if no proper interface is found, otherwise the interface that
  can be used to send the system packet from.

--*/
{
  IP4_INTERFACE             *IpIf;
  IP4_INTERFACE             *Selected;
  NET_LIST_ENTRY            *Entry;

  //
  // Select the interface the Dst is on if one of the connected 
  // network. Some IP instance may be configured with 0.0.0.0/0,
  // don't select that interface now.
  //
  IpIf = Ip4FindNet (IpSb, Dst);
  
  if ((IpIf != NULL) && (IpIf->Ip != IP4_ALLZERO_ADDRESS)) {
    return IpIf;
  }
  
  //
  // If source is one of the interface address, select it.
  //
  IpIf = Ip4FindInterface (IpSb, Src);
  
  if ((IpIf != NULL) && (IpIf->Ip != IP4_ALLZERO_ADDRESS)) {
    return IpIf;
  }
  
  //
  // Select a configured interface as the fall back. Always prefer
  // an interface with non-zero address.
  //
  Selected = NULL;
  
  NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
    IpIf = NET_LIST_USER_STRUCT (Entry, IP4_INTERFACE, Link);

    if (IpIf->Configured && ((Selected == NULL) || (Selected->Ip == 0))) {
      Selected = IpIf;
    }
  }

  return Selected;
}

VOID
Ip4SysPacketSent (
  IP4_PROTOCOL              *Ip4Instance,
  NET_BUF                   *Packet,
  EFI_STATUS                IoStatus,
  UINT32                    LinkFlag,
  VOID                      *Context
  )
/*++

Routine Description:

  The default callback function for system generated packet.
  It will free the packet. 

Arguments:

  Ip4Instance - The IP4 child that issued the transmission. 
                It most like is NULL.             
  Packet      - The packet that transmitted.
  IoStatus    - The result of the transmission, succeeded or failed.
  LinkFlag    - Not used when transmission. check IP4_FRAME_CALLBACK for reference.
  Context     - The context provided by us

Returns:

  None

--*/
{
  NetbufFree (Packet);
}

EFI_STATUS
Ip4Output (
  IN IP4_SERVICE            *IpSb,
  IN IP4_PROTOCOL           *IpInstance, OPTIONAL
  IN NET_BUF                *Packet,  
  IN IP4_HEAD               *Head,
  IN UINT8                  *Option,
  IN UINT32                 OptLen,
  IN IP4_ADDR               GateWay,
  IN IP4_FRAME_CALLBACK     Callback,
  IN VOID                   *Context
  )
/*++

Routine Description:

  Transmit an IP4 packet. The packet comes either from the IP4 
  child's consumer (IpInstance != NULL) or the IP4 driver itself 
  (IpInstance == NULL). It will route the packet, fragment it, 
  then transmit all the fragments through some interface.

Arguments:

  IpSb        - The IP4 service instance to transmit the packet
  IpInstance  - The IP4 child that issues the transmission. 
                It is NULL if the packet is from the system.
  Packet      - The user data to send, excluding the IP header.
  Head        - The caller supplied header. The caller should set the 
                following header fields: Tos, TotalLen, Id, tl, Fragment,
                Protocol, Src and Dst. All the fields are in host byte 
                order. This function will fill in the Ver, HeadLen, 
                Fragment, and checksum. The Fragment only need to include
                the DF flag. Ip4Output will compute the MF and offset for 
                you.
  Option      - The original option to append to the IP headers
  OptLen      - The length of the option
  GateWay     - The next hop address to transmit packet to. 255.255.255.255
                means broadcast.
  Callback    - The callback function to issue when transmission completed.
  Context     - The opaque context for the callback

Returns:

  EFI_NO_MAPPING - There is no interface to the destination.
  EFI_NOT_FOUND  - There is no route to the destination
  EFI_SUCCESS    - The packet is successfully transmitted.
  Others         - Failed to transmit the packet.

--*/
{
  IP4_INTERFACE             *IpIf;
  IP4_ROUTE_CACHE_ENTRY     *CacheEntry;
  IP4_ADDR                  Dest;
  EFI_STATUS                Status;
  NET_BUF                   *Fragment;
  UINT32                    Index;
  UINT32                    HeadLen;
  UINT32                    PacketLen;
  UINT32                    Offset;
  UINT32                    Mtu;
  UINT32                    Num;

  //
  // Select an interface/source for system packet, application 
  // should select them itself.
  //
  if (IpInstance == NULL) {
    IpIf = Ip4SelectInterface (IpSb, Head->Dst, Head->Src);
  } else {
    IpIf = IpInstance->Interface;
  }

  if (IpIf == NULL) {
    return EFI_NO_MAPPING;
  }

  if ((Head->Src == IP4_ALLZERO_ADDRESS) && (IpInstance == NULL)) {
    Head->Src = IpIf->Ip;
  }
  
  //
  // Route the packet unless overrided, that is, GateWay isn't zero.
  //
  if (GateWay == IP4_ALLZERO_ADDRESS) {
    Dest = Head->Dst;
    
    if (IP4_IS_BROADCAST (Ip4GetNetCast (Dest, IpIf)) || (Dest == IP4_ALLONE_ADDRESS)) {
      //
      // Set the gateway to local broadcast if the Dest is 
      // is the broadcast address for the connected network
      // or it is local broadcast.
      //
      GateWay = IP4_ALLONE_ADDRESS;

    } else if (IP4_IS_MULTICAST (Dest)) {
      //
      // Set the gateway to the destination if it is an multicast
      // address. The IP4_INTERFACE won't consult ARP to send local
      // broadcast and multicast. 
      //
      GateWay = Head->Dst;

    } else {
      //
      // Consult the route table to route the packet
      //
      if (IpInstance == NULL) {
        CacheEntry = Ip4Route (IpSb->DefaultRouteTable, Head->Dst, Head->Src);
      } else {
        CacheEntry = Ip4Route (IpInstance->RouteTable, Head->Dst, Head->Src);
      }

      if (CacheEntry == NULL) {
        return EFI_NOT_FOUND;
      }

      GateWay = CacheEntry->NextHop;
      Ip4FreeRouteCacheEntry (CacheEntry);
    }
  }
  
  //
  // OK, selected the source and route, fragment the packet then send
  // them. Tag each fragment other than the first one as spawn from it.
  //
  Mtu            = IpSb->SnpMode.MaxPacketSize;
  HeadLen        = sizeof (IP4_HEAD) + ((OptLen + 3) & (~0x03));
  Head->Id       = mIp4Id++;
  
  if (Packet->TotalSize + HeadLen > Mtu) {
    //
    // Packet is fragmented from the tail to the head, that is, the
    // first frame sent is the last fragment of the packet. The first
    // fragment is NOT sent in this loop. First compute how many 
    // fragments there are.
    //
    Mtu       = (Mtu - HeadLen) & (~0x07);
    Num       = (Packet->TotalSize + Mtu - 1) / Mtu;

    //
    // Initialize the packet length and Offset. Other than the last 
    // fragment, the packet length equals to MTU. The offset is always
    // aligned to MTU.
    //
    PacketLen = Packet->TotalSize - (Num - 1) * Mtu;
    Offset    = Mtu * (Num - 1);
    
    for (Index = 0; Index < Num - 1; Index++, Offset -= Mtu) {
      Fragment = NetbufGetFragment (Packet, Offset, PacketLen, IP4_MAX_HEADLEN);

      if (Fragment == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto ON_ERROR;
      }

      //
      // Update the header's fragment. The caller fills the IP4 header
      // fields that are required by Ip4PrependHead except the fragment.
      //
      Head->Fragment = IP4_HEAD_FRAGMENT_FIELD (FALSE, (Index != 0), Offset);
      Ip4PrependHead (Fragment, Head, Option, OptLen);

      //
      // Transmit the fragments, pass the Packet address as the context.
      // So, we can find all the fragments spawned from the Packet by 
      // compare the NetBuf and Context to the Packet.
      //
      Status = Ip4SendFrame (
                 IpIf, 
                 IpInstance, 
                 Fragment, 
                 GateWay, 
                 Ip4SysPacketSent, 
                 Packet
                 );

      if (EFI_ERROR (Status)) {
        goto ON_ERROR;
      }

      PacketLen = Mtu;
    }

    //
    // Trim the already sent data, then adjust the head's fragment field.
    //
    NetbufTrim (Packet, Packet->TotalSize - Mtu, FALSE);
    Head->Fragment = IP4_HEAD_FRAGMENT_FIELD (FALSE, TRUE, 0);
  }
  
  //
  // Send the first fragment, it is either the orginal packet, or the 
  // first fragment of a fragmented packet. It seems that there is a subtle
  // bug here: what if the caller free the packet in Callback and IpIf (or 
  // MNP child used by that interface) still holds the fragments and try
  // to access the data? The caller can free the packet if it recycles the
  // consumer's (such as UDP) data in the Callback. But this can't happen. 
  // The detailed sequence is:
  // 1. for the packets generated by IP4 driver itself:
  //    The Callback is Ip4SysPacketSent, which is the same as the 
  //    fragments' callback. Ip4SysPacketSent simply calls NetbufFree 
  //    to release its reference to the packet. So, no problem for 
  //    system packets.
  //
  // 2. for the upper layer's packets (use UDP as an example):
  //    UDP requests the IP layer to transmit some data which is 
  //    wrapped in an asynchronous token, the token is wrapped 
  //    in IP4_TXTOKEN_WRAP by IP4. IP4 also wrap the user's data
  //    in a net buffer, which is Packet we get here. IP4_TXTOKEN_WRAP
  //    is bound with the Packet. It will only be freed when all
  //    the references to Packet have been released. Upon then, the 
  //    Packet's OnFree callback will release the IP4_TXTOKEN_WRAP, 
  //    and singal the user's recycle event. So, also no problem for 
  //    upper layer's packets.
  //
  Ip4PrependHead (Packet, Head, Option, OptLen);
  Status = Ip4SendFrame (IpIf, IpInstance, Packet, GateWay, Callback, Context);

  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  return EFI_SUCCESS;

ON_ERROR:
  Ip4CancelPacket (IpIf, Packet, Status);
  return Status;
}

STATIC
BOOLEAN
Ip4CancelPacketFragments (
  IP4_LINK_TX_TOKEN   *Frame,
  VOID                *Context
  )
/*++

Routine Description:

  The filter function to find a packet and all its fragments. 
  The packet's fragments have their Context set to the packet.
  
Arguments:

  Frame   - The frames hold by the low level interface
  Context - Context to the function, which is the packet. 

Returns:

  TRUE    - This is the packet to cancel or its fragments.
  FALSE   - This is unrelated packet.

--*/
{
  if ((Frame->Packet == (NET_BUF *) Context) || (Frame->Context == Context)) {
    return TRUE;
  }

  return FALSE;
}

VOID
Ip4CancelPacket (
  IN IP4_INTERFACE    *IpIf,
  IN NET_BUF          *Packet,
  IN EFI_STATUS       IoStatus
  )
/*++

Routine Description:

  Cancel the Packet and all its fragments.

Arguments:

  IpIf      - The interface from which the Packet is sent
  Packet    - The Packet to cancel
  IoStatus  - The status returns to the sender.

Returns:

  None

--*/
{
  Ip4CancelFrames (IpIf, IoStatus, Ip4CancelPacketFragments, Packet);
}
